/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package algorithm;

import java.awt.image.BufferedImage;

/**
 *
 * @author mwiatrowski
 */
public class SkinDetectionAlgorithm {

    private HSI hsi;
    private YCrCb ycrcb;

    public SkinDetectionAlgorithm() {
        this.hsi = new HSI();
        this.ycrcb = new YCrCb();
    }

    public BufferedImage detect(BufferedImage in) {
        BufferedImage out = new BufferedImage(in.getWidth(), in.getHeight(), BufferedImage.TYPE_INT_RGB);
        int red = 0x00FF0000;
        int[] pixels = new int[in.getWidth() * in.getHeight()];
        in.getRGB(0, 0, in.getWidth(), in.getHeight(), pixels, 0, in.getWidth());
        for (int i = 0; i < pixels.length; i++) {
            int c = pixels[i];
            int r = (c & 0x00FF0000) >> 16;
            int g = (c & 0x0000FF00) >> 8;
            int b = (c & 0x000000FF) >> 0;
            int a = 0;
            if (ycrcb.isSkinYCbCr(r, g, b)) {
                a += 1;
            }
            if (isSkinRGB(r, g, b)) {
                a += 1;
            }
            if (hsi.isSkinHSI(r, g, b)) {
                a += 1;
            }
            if (a == 3) { // if all the methods respond "true", mark the pixel
                pixels[i] = red;
            }
        }
        out.setRGB(0, 0, in.getWidth(), in.getHeight(), pixels, 0, in.getWidth());
        return out;
    }

    public boolean isSkinRGB(int r, int g, int b) {
        // first easiest comparisons
        if ((r < 95) | (g < 40) | (b < 20) | (r < g) | (r < b)) {
            return false; // no match, stop function here
        }
        int d = r - g;
        if (-15 < d && d < 15) {
            return false; // no match, stop function here
        }
        // we have left most time consuming operation last
        // hopefully most of the time we are not reaching this point
        int max = max(r, g, b);
        int min = min(r, g, b);
        if ((max - min) < 15) {
            // this is the worst case
            return false; // no match, stop function 
        }
        // all comparisons passed
        return true;
    }

    private int min(int r, int g, int b) {
        return Math.min(Math.min(r, g), b);
    }

    private int max(int r, int g, int b) {
        return Math.max(Math.max(r, g), b);
    }

    protected class HSI {

        double h = 0.0;
        double s = 0.0;
        double i = 0.0;

        public HSI RGB2HSI(double R, double G, double B) {
            HSI result = new HSI();
            result.i = (R + G + B) / 3.0;    // we have calculated I!
            if (R == G && G == B) {
                return result;    // return result with h=0.0 and s=0.0
            }
            double r = R / i;            // normalize R
            double g = G / i;            // normalize G
            double b = B / i;            // normalize B
            double w = 0.5 * (R - G + R - B) / Math.sqrt((R - G) * (R - G) + (R - B) * (G - B));
            if (w > 1) {
                w = 1.0;       // clip input for acos to -1 <= w <= 1
            }
            if (w < -1) {
                w = -1.0;     // clip input for acos to -1 <= w <= 1
            }
            result.h = Math.acos(w);   // the value is 0 <= h <= Math.PI
            if (B > G) {
                result.h = 2 * Math.PI - result.h;
            }
            // finally the last component s
            result.s = 1 - 3 * min(r, g, b);
            return result;
        }

        public boolean isSkinHSI(double r, double g, double b) {
            HSI hsi = RGB2HSI(r, g, b);
            if (hsi.h < 25 || hsi.h > 230) {
                return true;
            }
            return false;
        }

        private double min(double r, double g, double b) {
            return Math.min(Math.min(r, g), b);
        }
    }

    protected class YCrCb {

        double Y = 0.0;
        double Cr = 0.0;
        double Cb = 0.0;

        public YCrCb RGB2YCbCr(double R, double G, double B) {
            YCrCb result = new YCrCb();
            result.Y = (0.257 * R) + (0.504 * G) + (0.098 * B) + 16;
            result.Cr = (0.439 * R) - (0.368 * G) - (0.071 * B) + 128;
            result.Cb = -(0.148 * R) - (0.291 * G) + (0.439 * B) + 128;
            return result;
        }

        public boolean isSkinYCbCr(double r, double g, double b) {
            YCrCb result = RGB2YCbCr(r, g, b);
            double cr = result.Cr;
            double cb = result.Cb;
            if (cr >= ((1.5862 * cb) + 20)) {
                return false;
            }
            if (cr <= ((0.3448 * cb) + 76.2069)) {
                return false;
            }
            if (cr <= ((-4.5652 * cb) + 234.5652)) {
                return false;
            }
            if (cr >= ((-1.15 * cb) + 301.75)) {
                return false;
            }
            if (cr >= ((-2.2857 * cb) + 432.85)) {
                return false;
            }
            // all comparisons passed
            return true;
        }
    }
}
